<?php 

namespace pmvc\mvc\filter;

use pmvc\mvc\MappedObject;

use pmvc\log\Log;
use pmvc\util\StringUtil;
use pmvc\mvc\Dispatcher;
use pmvc\mvc\Controller;
use pmvc\mvc\Response;
use pmvc\mvc\Request;
use pmvc\mvc\RequestChain;
use pmvc\mvc\ModelUtil;
use Exception;

/**
 * {@link RequestFilter} for handling {@link Controller}s.  Typically
 * used at the end of a {@link RequestChain} to invoke a {@link Controller}.
 */
class ControllerRequestFilter
	extends AbstractRequestFilter {

    public function handleRequest(RequestChain $chain, Request $request, Response $response) {

    	// get the dispatcher
    	$dispatcher = $request->getDispatcher();
    	$appCtx = $dispatcher->getApplicationContext();

    	// get the controller 
    	$controllerMappings = $appCtx->getObjectsOfType("pmvc\mvc\ControllerMapping");
    	$mappedObject = $this->getController($controllerMappings, $request);

    	// send to 500 if appropriate
    	if (!isset($mappedObject)
    		&& $request->getRequestedUrl()==Dispatcher::ERROR_VIEWNAME_PREFIX.'500') {
			$mappedObject = new MappedObject(
    			new \pmvc\mvc\controller\ExceptionController(),
    			Dispatcher::ERROR_VIEWNAME_PREFIX.'500');

    	// send to 404 if appropriate
    	} else if (!isset($mappedObject)
    		&& $request->getRequestedUrl()==Dispatcher::ERROR_VIEWNAME_PREFIX.'404') {
			$mappedObject = new MappedObject(
    			new \pmvc\mvc\controller\FourOhFourController(),
    			Dispatcher::ERROR_VIEWNAME_PREFIX.'404');

    	// forward to 404 handler
    	} else if (!isset($mappedObject)) {
    		Log::error(__CLASS__, "404 not found: {0}", $request->getRequestedUrl());
    		$request->setAttribute("url", $request->getRequestedUrl());
    		$dispatcher->forward(Request::factory($request, Array(
    			Request::URI => Dispatcher::ERROR_VIEWNAME_PREFIX.'404'
    			)), $response);
    		return;
    	}

    	// add path variables
    	$request->addPathVariables($mappedObject);

    	// execute the controller and get the view name
    	$viewName = $mappedObject->getObject()->handleRequest($request, $response);

    	// get the view handler and render the view
    	if (!empty($viewName)) {
    		
    		// check for redirect
    		if (StringUtil::startsWith($viewName, Dispatcher::REDIRECT_VIEWNAME_PREFIX)) {
    			$toUrl = substr($viewName, strlen(Dispatcher::REDIRECT_VIEWNAME_PREFIX));
    			$toUrl = ModelUtil::stringReplace($toUrl, $response->getModel());
    			if (!StringUtil::startsWith($toUrl, "http", true)
    				&& !StringUtil::startsWith($toUrl, "/")) {
    				$toUrl = $request->getBaseUrl()."/".$toUrl;
    			}
    			Log::debug(__CLASS__, "Redirecting to {0}", $toUrl);
    			$response->sendRedirect($toUrl);
    			return;
    		
    		// check for forward
    		} else if (StringUtil::startsWith($viewName, Dispatcher::FORWARD_VIEWNAME_PREFIX)) {
    			$toUrl = substr($viewName, strlen(Dispatcher::FORWARD_VIEWNAME_PREFIX));
    			$toUrl = ModelUtil::stringReplace($toUrl, $response->getModel());
    			Log::debug(__CLASS__, "Forwarding to {0}", $toUrl);
    			$request = Request::factory($request, Array(
    				Request::URI => $toUrl
    			));
    			$request->getDispatcher()->forward($request, $response);
    			return;

    		// use view handler
    		} else {
		    	$viewHandlerMappings = $appCtx->getObjectsOfType('pmvc\mvc\ViewHandlerMapping');
		    	foreach ($viewHandlerMappings as $viewHandlerMapping) {
		    		$mappedObject = $viewHandlerMapping->getViewHandler($viewName, $request);
		    		if (isset($mappedObject)) {
		    			Log::trace(__CLASS__, "ViewHandler: {0}", get_class($mappedObject->getObject()));
		    			$mappedObject->getObject()->renderView($request, $response, $viewName);
		    			$chain->next($request, $response);
		    			return;
		    		}
		    	}

		    	// no view handler found
		    	Log::warn(__CLASS__, "No ViewHandler configured for view {0}", $viewName);
    			$chain->next($request, $response);
    			return;
    		}

    	}

    	// no view name from the controller
    	// go next, likely this is the end of the line though
		Log::debug(__CLASS__, "No view name returned from controller, assuming controller handled it");
    	$chain->next($request, $response);
    }

    /**
     * Gets a controller for a URL.
     * @param Array $controllerMappings
     * @param $url the requested url
     * @param string $url
     */
    private function getController(Array $controllerMappings, Request $request, $url=null) {
    	if (!isset($url)) {
    		$url = $request->getRequestedUrl();
    	}
        foreach ($controllerMappings as $controllerMapping) {
    		$mappedObject = $controllerMapping->getController($url, $request);
    		if (isset($mappedObject)) {
    			return $mappedObject;
    		}
    	}
    	return null;
    }
	
}

?>